// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_error.h"
#include "mk_model.h"
#include "mk_skeleton.h"
#include "mk_resource_manager.h"
#include "mk_engine.h"
#include "mk_math.h"
#include "mk_aabb.h"
#include "mk_file_system.h"
#include "mk_memory_block.h"
#include "mk_timer.h"

namespace MK3DE
{
	CModel::CModel()
	{
		_pSkeleton = NULL;
		_vertexBufferState = VB_DIRTY_NONE;
		_lastVertexDataChangedTime = 0;
		_bPause = FALSE;
	}

	CModel::~CModel()
	{
		for (int i = 0; i < (int)_vecSubMesh.size(); ++i)
			SAFE_RELEASE(_vecSubMesh[i]);

		_vecSubMesh.clear();

		for (int i = 0; i < (int)_vecSkeleton.size(); ++i)
			SAFE_DELETE(_vecSkeleton[i]);

		_vecSkeleton.clear();

		_pSkeleton = NULL;
	}

	void CModel::SetAnimationInterval(float time)
	{
		for (int i = 0; i < (int)_vecSkeleton.size(); ++i)
			_vecSkeleton[i]->SetInterval(time);
	}

	void CModel::CalculateSkeletonBaseTranformation()
	{
		if (_vecSkeleton.size() > 0)
		{
			CSkeleton* pBaseSkeleton = _vecSkeleton[0];
			pBaseSkeleton->CalculateBaseTransformation(NULL);
			for (int i = 1; i < (int)_vecSkeleton.size(); ++i)
			{
				CSkeleton* skeleton = _vecSkeleton[i];
				skeleton->CalculateBaseTransformation(pBaseSkeleton);
			}

			UseAnimation(0, 0);
		}
	}

	BOOL CModel::AddSubMesh(const char* fileName, int materialIdx)
	{
		BOOL ret = 0;

		CMesh* mesh = NULL;
		CResourceManager* rm = GetEngine()->GetResourceManager();
		ret = rm->GetMesh(fileName, mesh);
		MK_PROCESS_ERROR(ret);

		mesh->SetMaterialIdx(materialIdx);

		_vecSubMesh.push_back(mesh);

		return TRUE;
Exit0:
		return FALSE;
	}

	BOOL CModel::AddSkeleton(const char* fileName)
	{
		CSkeleton *pSkeleton = new CSkeleton();
		MK_PROCESS_ERROR(pSkeleton);

		BOOL ret = pSkeleton->LoadFromFile(fileName);
		MK_PROCESS_ERROR(ret);

		_vecSkeleton.push_back(pSkeleton);

		return TRUE;
Exit0:
		SAFE_DELETE(pSkeleton);
		return FALSE;
	}

	void CModel::UpdateSkeleton(DWORD dt, BOOL bChanged)
	{
		if (_bPause)
			return;

		if (_pSkeleton)
		{
			_pSkeleton->Update(dt, bChanged); 
			_vertexBufferState |= VB_DIRTY_POS_NORMAL;
		}
	}

	DWORD CModel::GetAnimationCount()
	{
		return (DWORD)_vecSkeleton.size();
	}

	BOOL CModel::UseAnimation(DWORD idx, float time)
	{
		if (idx >= _vecSkeleton.size())
			return FALSE;

		CSceneNode* parent = NULL;
		CBone* bone = NULL;
		if (_pSkeleton)
		{
			bone = _pSkeleton->GetBone(0);
			parent = bone->GetParent();
			bone->SetParent(NULL);
		}

		_pSkeleton = _vecSkeleton[idx];
		if (time >= 0)
			_pSkeleton->SetTime(time);

		_pSkeleton->GetBone(0)->SetParent(parent);

		return TRUE;
	}

	BOOL CModel::GetSkeleton(DWORD idx, /* out */ CSkeleton*& pSkeleton)
	{
		if (idx >= _vecSkeleton.size())
			return FALSE;

		pSkeleton = _vecSkeleton[idx];
		return TRUE;
	}

	DWORD CModel::PrepareUpdateVertexData()
	{
		CTimer* timer = GetEngine()->GetTimer();
		DWORD now = timer->GetMilliseconds();
		if (now - _lastVertexDataChangedTime < 1)
			return FALSE;

		if (_vertexBufferState != VB_DIRTY_NONE)
			_lastVertexDataChangedTime = now;

		DWORD lastVertexBufferState = _vertexBufferState;
		_vertexBufferState = VB_DIRTY_NONE;
		return lastVertexBufferState;
	}

	int CModel::GetVertexCount()
	{
		int cnt = 0;
		for (int i = 0; i < (int)_vecSubMesh.size(); ++i)
			cnt += _vecSubMesh[i]->GetVertexCount();

		return cnt;
	}

	int CModel::GetFaceCount()
	{
		int cnt = 0;
		for (int i = 0; i < (int)_vecSubMesh.size(); ++i)
			cnt += _vecSubMesh[i]->GetFaceCount();

		return cnt;
	}

	int CModel::FillVertexBuffer(CMesh::Position* pPosition, CMesh::Normal* pNormal, int vertexNum)
	{
		for (int i = 0; i < (int)_vecSubMesh.size(); ++i)
		{
			CMesh* mesh = _vecSubMesh[i];
			int cnt = mesh->GetVertexCount();
			if (!mesh->FillVertexBuffer(_pSkeleton, pPosition, pNormal, cnt))
				return 0;

			pPosition += cnt;
			pNormal += cnt;
		}

		return 1;
	}

	int CModel::FillUVBuffer(CMesh::TextureCoord* puv, int vertexNum)
	{
		for (int i = 0; i < (int)_vecSubMesh.size(); ++i)
		{
			CMesh* mesh = _vecSubMesh[i];
			int cnt = mesh->GetVertexCount();
			if (!mesh->FillUVBuffer(puv, cnt))
				return 0;

			puv += cnt;
		}

		return 1;
	}

	int CModel::FillSkinBuffer(D3DXVECTOR4* pIndex, D3DXVECTOR4* pWeight, int vertexNum)
	{
		for (int i = 0; i < (int)_vecSubMesh.size(); ++i)
		{
			CMesh* mesh = _vecSubMesh[i];
			int cnt = mesh->GetVertexCount();
			if (!mesh->FillSkinBuffer(pIndex, pWeight, cnt))
				return 0;

			pIndex += cnt;
			pWeight += cnt;
		}

		return 1;
	}

	int CModel::FillFaceBuffer(CMesh::Face* pFace, int faceNum)
	{
		int vertex_offset = 0;
		for (int i = 0; i < (int)_vecSubMesh.size(); ++i)
		{
			CMesh* mesh = _vecSubMesh[i];
			int face_cnt = mesh->GetFaceCount();
			if (!mesh->FillFaceBuffer(pFace, face_cnt, vertex_offset))
				return 0;

			vertex_offset += mesh->GetVertexCount();
			pFace += face_cnt;
		}

		return 1;
	}

	CMesh* CModel::GetSubMeshByIdx(int idx)
	{
		if (idx >= 0 && idx < (int)_vecSubMesh.size())
			return _vecSubMesh[idx];
		return NULL;
	}

	void CModel::SortSubMeshByMaterialIdx(int materialCount, std::vector<int>& groupSubMeshCount)
	{
		groupSubMeshCount.clear();
		std::vector<CMesh*> vecMesh;
		for (int i = 0; i < materialCount; ++i)
		{
			int count = 0;
			for (int iSubMesh = 0; iSubMesh < (int)_vecSubMesh.size(); ++iSubMesh)
			{
				CMesh* subMesh = _vecSubMesh[iSubMesh];
				int mtlIdx = subMesh->GetMaterialIdx();
				if (mtlIdx >= materialCount)
					continue;

				if (mtlIdx == i)
				{
					vecMesh.push_back(subMesh);
					count++;
				}
			}

			groupSubMeshCount.push_back(count);
		}

		for (int iSubMesh = 0; iSubMesh < (int)_vecSubMesh.size(); ++iSubMesh)
		{
			CMesh* subMesh = _vecSubMesh[iSubMesh];
			int mtlIdx = subMesh->GetMaterialIdx();
			if (mtlIdx >= materialCount)
				vecMesh.push_back(subMesh);
		}

		_vecSubMesh = vecMesh;
		_vertexBufferState |= VB_DIRTY_FACE_INDEX;
		_vertexBufferState |= VB_DIRTY_POS_NORMAL;
		_vertexBufferState |= VB_DIRTY_TEXCOORD;
	}

	BOOL CModel::CollideWithRay(D3DXMATRIX& mat, D3DXVECTOR3& pos, float x, float y, float z, D3DXVECTOR3& dir)
	{
		std::vector<CMesh*>::iterator i = _vecSubMesh.begin();
		while (i != _vecSubMesh.end())
		{
			CMesh* mesh = *i;
			if (mesh->CollideWithRay(mat, pos, x, y, z, dir))
				return TRUE;

			++i;
		}

		return FALSE;
	}
}